#if defined mystreamer_inc
 #endscript
#endif
#define mystreamer_inc

#include <a_objects>
#include <a_players>
#tryinclude <a_defines>

#if defined ENABLE_LOD_SUPPORT
 #tryinclude <lod>
#endif

#tryinclude num_players

#define STREAM_TYPE_SPHERE 	1
#define STREAM_TYPE_CIRCLE 	2
//#define STREAM_TYPE_CUBE 	3
//#define STREAM_TYPE_SQUARE 	4


//#define ENABLE_LOD_SUPPORT


#if !defined STREAM_TYPE
 #define STREAM_TYPE STREAM_TYPE_SPHERE
#endif

#if !defined MAX_STREAM_OBJECTS
 #define MAX_STREAM_OBJECTS 50
#endif



#if defined ENABLE_LOD_SUPPORT
 #if !defined LOD_VIEW_DISTANCE
  #define LOD_VIEW_DISTANCE 420
 #endif

 #if !defined LOD_HIDE_VIEW_DISTANCE
  #define LOD_HIDE_VIEW_DISTANCE 160
 #endif


 #if !defined LOD_SQ_VIEW_DISTANCE // must be constant if int
  #define LOD_SQ_VIEW_DISTANCE ((LOD_VIEW_DISTANCE)*(LOD_VIEW_DISTANCE))
 #endif

 #if !defined LOD_HIDE_SQ_VIEW_DISTANCE // must be constant if int
  #define LOD_HIDE_SQ_VIEW_DISTANCE ((LOD_HIDE_VIEW_DISTANCE)*(LOD_HIDE_VIEW_DISTANCE))
 #endif

 #if !defined HQ_VIEW_DISTANCE
  #define HQ_VIEW_DISTANCE 220
#endif

 #if !defined HQ_SQ_VIEW_DISTANCE // must be constant if int
  #define HQ_SQ_VIEW_DISTANCE ((HQ_VIEW_DISTANCE)*(HQ_VIEW_DISTANCE))
 #endif
#endif


#if !defined VIEW_DISTANCE
 #define VIEW_DISTANCE 300 // int to optimize
#endif

#if !defined SQ_VIEW_DISTANCE // must be constant if int
 #define SQ_VIEW_DISTANCE ((VIEW_DISTANCE)*(VIEW_DISTANCE))
#endif




#if !defined MAX_OBJECTS_PER_PLAYER // max objects that player can see
 #define MAX_OBJECTS_PER_PLAYER MAX_OBJECTS // max std sa-mp objects amount
#endif

// anti-overflow

#define MYST_NOPACK (cellbits/8) // amount of bytes per cell


#if MAX_OBJECTS_PER_PLAYER <= 0x100 // 256
// #define MYST_PACK_BYTES 1 // 4 values per cell
#elseif MAX_OBJECTS_PER_PLAYER <= 0x10000 // 65536
// #define MYST_PACK_BYTES 2 // 2 values per cell
#else
// #define MYST_PACK_BYTES MYST_NOPACK // no pack - 1 value per cell
#endif
#define MYST_PACK_BYTES MYST_NOPACK // unexpected high lag


#define MYST_PACK_FACTOR (MYST_NOPACK/MYST_PACK_BYTES)

#if MYST_PACK_BYTES == MYST_NOPACK
 #assert MYST_PACK_FACTOR == 1
#endif

//#assert MAX_OBJECTS_PER_PLAYER <= (2^(MYST_PACK_BYTES*8))

#define MYST_PACK(%0) (((%0)-1)/MYST_PACK_FACTOR+1)


enum objstruct
{
	e_modelid, // model
	#if defined ENABLE_LOD_SUPPORT
	e_LODelid, // lod model
	#endif
	Float:e_x, // pos
	Float:e_y,
	Float:e_z,
	e_ox, // offsets
	e_oy,
	#if STREAM_TYPE == STREAM_TYPE_SPHERE
	e_oz,
	#endif
	e_ow, // main constant
	Float:e_rx, // rotating
	Float:e_ry,
	Float:e_rz,
	#if defined ENABLE_LOD_SUPPORT
	e_lodobjid[MYST_PACK(MAX_PLAYERS)], // lod object id - packed
	#endif
	e_objectid[MYST_PACK(MAX_PLAYERS)] // hq object id - packed
};

static stock streamer_objects[MAX_STREAM_OBJECTS][objstruct];

static object_count = -1;

//forward StreamObjects();

stock StreamObjects()
{
	new Float:xx, Float:yy, Float:zz;
	for(new playerid=0; playerid<MAX_PLAYERS; playerid++)
		if(GetPlayerPos(playerid, xx, yy, zz) && (xx != 0.0 && yy != 0.0))
		{
			StreamRangeObjects(xx, yy, zz, playerid);
		}
	return true;
}

#define StreamPlayerObjects(%0,%1,%2,%3) StreamRangeObjects(%1,%2,%3,%0)
stock StreamRangeObjects(Float:x, Float:y, Float:z, extra)
{
	new ix = floatround(x);
	new iy = floatround(y);
	#if STREAM_TYPE == STREAM_TYPE_SPHERE
	new iz = floatround(z);
	new offset = ix * ix + iy * iy + iz * iz;
	#else
	#pragma unused z
	new offset = ix * ix + iy * iy;
	#endif
	for(new j = 0; j <= object_count; ++j)
	{
		//calculate the distance to the object
		#if STREAM_TYPE == STREAM_TYPE_SPHERE
		offset += streamer_objects[j][e_ow] + ix * streamer_objects[j][e_ox] +
			iy * streamer_objects[j][e_oy] +
			iz * streamer_objects[j][e_oz];
		#else // circle
		offset += streamer_objects[j][e_ow] + ix * streamer_objects[j][e_ox] +
			iy * streamer_objects[j][e_oy];
		#endif
		// we now have the squared distance to the object
		#if defined ENABLE_LOD_SUPPORT 
		// incoming: LOD show(1) -> HQ show(2) -> LOD hide(3) 
		// outcoming: -> LOD show(4) -> HQ hide(5) -> LOD hide(6)
		if(streamer_objects[j][e_modelid] != streamer_objects[j][e_LODelid])
		{ 	// this object has LOD model
			if (offset < HQ_SQ_VIEW_DISTANCE)
			{
				if(offset < LOD_HIDE_SQ_VIEW_DISTANCE)
					HideLODObjectForPlayer(j, extra); // (3)
				else
					ShowLODObjectForPlayer(j, extra); // (4)
				ShowStreamObjectForPlayer(j, extra); // (2)
			}
			else if (offset < LOD_SQ_VIEW_DISTANCE)
			{
				HideStreamObjectForPlayer(j, extra); // (5)
				ShowLODObjectForPlayer(j, extra); // (1)
			}
			else
			{
				HideStreamObjectForPlayer(j, extra); // (5)
				HideLODObjectForPlayer(j, extra); // (6)
			}
		}
		else // no LOD model available
		#endif // ^ or LOD disabled
		{ 	// incoming: MQ show(1) outcoming: MQ hide(2)
			if (offset < SQ_VIEW_DISTANCE) // player nearby object
			{
				ShowStreamObjectForPlayer(j, extra); // (1)
			}
			else // player is too far away from object
			{
				HideStreamObjectForPlayer(j, extra); // (2)
			}
		}
		//#endif
	}
}

#if MYST_PACK_BYTES == MYST_NOPACK
ShowStreamObjectForPlayer(sobjectid, playerid)
{
	if(streamer_objects[sobjectid][e_objectid][playerid] == 0)
	{
		if
		(
			(
				streamer_objects[sobjectid][e_objectid][playerid] =
					CreatePlayerObject(playerid, streamer_objects[sobjectid][e_modelid],
						streamer_objects[sobjectid][e_x], streamer_objects[sobjectid][e_y], streamer_objects[sobjectid][e_z],
						streamer_objects[sobjectid][e_rx], streamer_objects[sobjectid][e_ry], streamer_objects[sobjectid][e_rz])
			) == INVALID_OBJECT_ID
		)
			return(streamer_objects[sobjectid][e_objectid][playerid] = 0);
		return true;
	}
	return false;
}


HideStreamObjectForPlayer(sobjectid, playerid)
{
	if(streamer_objects[sobjectid][e_objectid][playerid] != 0)
	{
		DestroyPlayerObject(playerid, streamer_objects[sobjectid][e_objectid][playerid]);
		streamer_objects[sobjectid][e_objectid][playerid] = 0;
		return true;
	}
	return false;
}
#else

ShowStreamObjectForPlayer(sobjectid, playerid)
{
	if(getbyte(streamer_objects[sobjectid][e_objectid], playerid) == 0)
	{
		new tmp = CreatePlayerObject(playerid, streamer_objects[sobjectid][e_modelid],
			streamer_objects[sobjectid][e_x], streamer_objects[sobjectid][e_y], streamer_objects[sobjectid][e_z],
			streamer_objects[sobjectid][e_rx], streamer_objects[sobjectid][e_ry], streamer_objects[sobjectid][e_rz]);
		//printf("HQ %d: id %d oid: %d model %d", playerid, sobjectid, tmp, streamer_objects[sobjectid][e_modelid]);
		if(tmp < MAX_OBJECTS_PER_PLAYER) // valid
		{
			DestroyObject(tmp);
			return(setbyte(streamer_objects[sobjectid][e_objectid], playerid, tmp));
		}
	}
	return false;
}


HideStreamObjectForPlayer(sobjectid, playerid)
{
	if(getbyte(streamer_objects[sobjectid][e_objectid], playerid) != 0)
	{
		DestroyPlayerObject(playerid, getbyte(streamer_objects[sobjectid][e_objectid], playerid));
		setbyte(streamer_objects[sobjectid][e_objectid], playerid, 0);
		return true;
	}
	return false;
}

#endif

#if defined ENABLE_LOD_SUPPORT


#if MYST_PACK_BYTES == MYST_NOPACK

ShowLODObjectForPlayer(sobjectid, playerid)
{
	if(streamer_objects[sobjectid][e_lodobjid][playerid] == 0)
	{
		if
		(
			(
				streamer_objects[sobjectid][e_lodobjid][playerid] =
					CreatePlayerObject(playerid, streamer_objects[sobjectid][e_LODelid],
						streamer_objects[sobjectid][e_x], streamer_objects[sobjectid][e_y], streamer_objects[sobjectid][e_z],
						streamer_objects[sobjectid][e_rx], streamer_objects[sobjectid][e_ry], streamer_objects[sobjectid][e_rz])
			) == INVALID_OBJECT_ID
		)
			return(streamer_objects[sobjectid][e_lodobjid][playerid] = 0);
		return true;
	}
	return false;
}


HideLODObjectForPlayer(sobjectid, playerid)
{
	if(streamer_objects[sobjectid][e_lodobjid][playerid] != 0)
	{
		DestroyPlayerObject(playerid, streamer_objects[sobjectid][e_lodobjid][playerid]);
		streamer_objects[sobjectid][e_lodobjid][playerid] = 0;
		return true;
	}
	return false;
}

#else


ShowLODObjectForPlayer(sobjectid, playerid)
{
	if(!getbyte(streamer_objects[sobjectid][e_lodobjid], playerid)/* == 0*/)
	{
		new tmp = CreatePlayerObject(playerid, streamer_objects[sobjectid][e_LODelid],
			streamer_objects[sobjectid][e_x], streamer_objects[sobjectid][e_y], streamer_objects[sobjectid][e_z],
			streamer_objects[sobjectid][e_rx], streamer_objects[sobjectid][e_ry], streamer_objects[sobjectid][e_rz]);
		//printf("LOD %d: id %d oid: %d model %d", playerid, sobjectid, tmp, streamer_objects[sobjectid][e_LODelid]);
		if(tmp < MAX_OBJECTS_PER_PLAYER) // valid
			return(setbyte(streamer_objects[sobjectid][e_lodobjid], playerid, tmp));
	}
	return false;
}


HideLODObjectForPlayer(sobjectid, playerid)
{
	if(getbyte(streamer_objects[sobjectid][e_lodobjid], playerid)/* != 0*/)
	{
		DestroyPlayerObject(playerid, getbyte(streamer_objects[sobjectid][e_lodobjid], playerid));
		setbyte(streamer_objects[sobjectid][e_lodobjid], playerid, 0);
		return true;
	}
	return false;

}
#endif

#endif


#define PushStreamObject CreateStreamObject
stock CreateStreamObject(modelid, Float:x, Float:y, Float:z, Float:rx, Float:ry, Float:rz, lod = 0)
{
	new Float:ox, Float:oy;
	#if STREAM_TYPE == STREAM_TYPE_SPHERE
	new Float:oz;
	#endif
	if(object_count > (MAX_STREAM_OBJECTS-2)) return(-1);
	// Get the offset from the last object.
	if (object_count == -1)
	{
		ox = 0.0;
		oy = 0.0;
		#if STREAM_TYPE == STREAM_TYPE_SPHERE
		oz = 0.0;
		#endif
	}
	else
	{
		ox = streamer_objects[object_count][e_x];
		oy = streamer_objects[object_count][e_y];
		#if STREAM_TYPE == STREAM_TYPE_SPHERE
		oz = streamer_objects[object_count][e_z];
		#endif
	}
	new
		Float:dx = ox - x,
		Float:dy = oy - y;
	#if STREAM_TYPE == STREAM_TYPE_SPHERE
	new Float:dz = oz - z;
	#endif
	++object_count;
	// Store the offsets.
	streamer_objects[object_count][e_ox] = floatround(2 * dx);
	streamer_objects[object_count][e_oy] = floatround(2 * dy);
	#if STREAM_TYPE == STREAM_TYPE_SPHERE
	streamer_objects[object_count][e_oz] = floatround(2 * dz);
	#endif
	// Calculate the main constant.
	#if STREAM_TYPE == STREAM_TYPE_SPHERE
	streamer_objects[object_count][e_ow] = floatround(
			(dx * dx + dy * dy + dz * dz) - 2 *
			(ox * dx + oy * dy + oz * dz)
		);
	#else
	streamer_objects[object_count][e_ow] = floatround(
			(dx * dx + dy * dy) - 2 *
			(ox * dx + oy * dy)
		);
	#endif
	// Store the other data.
	streamer_objects[object_count][e_modelid] = modelid;

	#if defined ENABLE_LOD_SUPPORT

	if(!lod)
	#if defined lod_inc
		streamer_objects[object_count][e_LODelid] = FindLODObject(modelid);
	#else
		streamer_objects[object_count][e_LODelid] = modelid;
	#endif
	else
		streamer_objects[object_count][e_LODelid] = lod;
	#else
	 #pragma unused lod

	#endif

	streamer_objects[object_count][e_x] = x;
	streamer_objects[object_count][e_y] = y;
	streamer_objects[object_count][e_z] = z;
	streamer_objects[object_count][e_rx] = rx;
	streamer_objects[object_count][e_ry] = ry;
	streamer_objects[object_count][e_rz] = rz;
	// Erase previous data
	for(new i = 0; i < (MAX_PLAYERS-1)/4+1; i++)
	{
		streamer_objects[object_count][e_objectid][i] = 0;
		#if defined ENABLE_LOD_SUPPORT
		streamer_objects[object_count][e_lodobjid][i] = 0;
		#endif
	}
	return(object_count);
}

#define PopStreamObject DestroyStreamObject
stock DestroyStreamObject()
{
	if(object_count < 0) return(-1);
	for(new i=0; i < MAX_PLAYERS; i++)
	{
		HideStreamObjectForPlayer(object_count, i);
		#if defined ENABLE_LOD_SUPPORT
		HideLODObjectForPlayer(object_count, i);
		#endif
		//streamer_objects[object_count][e_objectid][i] = 0;
	}
	return(--object_count);
}

stock GetStreamObjectsCount() { return(object_count+1); }

stock IsValidStreamObject(sobjectid) { return(sobjectid<=object_count && sobjectid>=0); }

stock GetStreamObjectPos(sobjectid, &Float:x, &Float:y, &Float:z)
{
	if(!IsValidStreamObject(sobjectid)) return false;
	x = streamer_objects[sobjectid][e_x];
	y = streamer_objects[sobjectid][e_y];
	z = streamer_objects[sobjectid][e_z];
	return true;
}

stock SetStreamObjectPos(sobjectid, Float:x, Float:y, Float:z)
{
	if(!IsValidStreamObject(sobjectid)) return false;
	streamer_objects[sobjectid][e_x] = x;
	streamer_objects[sobjectid][e_y] = y;
	streamer_objects[sobjectid][e_z] = z;

	new objectid;
	for(new i = 0; i < MAX_PLAYERS; i++)
		#if defined ENABLE_LOD_SUPPORT
		if((objectid = getbyte(streamer_objects[sobjectid][e_objectid], i)) != 0 ||
			(objectid = getbyte(streamer_objects[sobjectid][e_lodobjid], i)) != 0)
		#else
		if((objectid = getbyte(streamer_objects[sobjectid][e_objectid], i)) != 0)
		#endif
			SetPlayerObjectPos(i, objectid, x, y, z);
	return true;
}


stock SetStreamObjectRot(sobjectid, Float:x, Float:y, Float:z)
{
	if(!IsValidStreamObject(sobjectid)) return false;
	streamer_objects[sobjectid][e_rx] = x;
	streamer_objects[sobjectid][e_ry] = y;
	streamer_objects[sobjectid][e_rz] = z;

	new objectid;
	for(new i = 0; i < MAX_PLAYERS; i++)
		#if defined ENABLE_LOD_SUPPORT
		if((objectid = getbyte(streamer_objects[sobjectid][e_objectid], i)) != 0 ||
			(objectid = getbyte(streamer_objects[sobjectid][e_lodobjid], i)) != 0)
		#else
		if((objectid = getbyte(streamer_objects[sobjectid][e_objectid], i)) != 0)
		#endif
			SetPlayerObjectRot(i, objectid, x, y, z);
	return true;
}

/*
stock myst_setplayerpos(playerid, Float:x, Float:y, Float:z)
{
	if(IsPlayerConncted(playerid))
	{
		StreamRangeObjects(x, y, z, VIEW_DISTANCE, playerid);
		return SetPlayerPos(playerid, x, y, z);
	}
	return false;
}
*/
stock ErasePlayerObjects(playerid)
{
	if(IsPlayerConnected(playerid))
	{
		for(new i = 0; i <= object_count; i++) 
		{
			
			if(getbyte(streamer_objects[i][e_objectid], playerid))
			{
				HideStreamObjectForPlayer(i, playerid);
			}
			#if defined ENABLE_LOD_SUPPORT
			if(getbyte(streamer_objects[i][e_lodobjid], playerid))
			{
				HideLODObjectForPlayer(i, playerid);
			}
			#endif
		}
		return true;
	}
	return false;
}



#if MYST_PACK_BYTES != MYST_NOPACK

#if !defined _bitmask
 #define _bitmask(%0) (((1)<<(%0))-(1))

 #define getbits(%0,%1,%2) (((%0)>>(%1))&_bitmask(%2))
 #define setbits(%0,%1,%2,%3) ((~(((~(%3))&_bitmask(%2))<<(%1)))&(((%3)<<(%1))|(%0)))
#endif



static stock setbyte(arr[], idx, val)
{
	new t;
	#if MYST_PACK_FACTOR == 2
	t = idx >> 1;
	#else
	t = idx / MYST_PACK_FACTOR;
	#endif
	arr[t] = setbits(arr[t], (idx % MYST_PACK_FACTOR) * 8, 8 * MYST_PACK_BYTES, val);
	return val;
}

static stock getbyte(arr[], idx)
{
	return getbits(arr[idx / MYST_PACK_FACTOR], (idx % MYST_PACK_FACTOR) * 8, MYST_PACK_BYTES * 8);
}
#else
static stock setbyte(arr[], idx, val)
{
	arr[idx] = val;
}

static stock getbyte(arr[], idx)
{
	return arr[idx];
}
#endif


